/////////////////////////////////////////////////////////////////////////////
// Purpose:     File access management
// Author:      Jesus Gonzalez
// Modified by:
// Copyright:   (c) 2003-2013 Jesus Gonzalez <jgonzalez@gdr-sistemas.com>
// License:     BSD License
/////////////////////////////////////////////////////////////////////////////

#include "SmartLib/File.h"

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "Log.h"

#include <sys/file.h>

#ifndef UTIL_FILE_READBUF_SIZE
#define UTIL_FILE_READBUF_SIZE	1024
#endif

File::File() : m_fd(-1)
{
}

File::File(const std::string &filename, File::EMode mode)
{
    m_fd = -1;
    Open(filename, mode);
}

File::~File()
{
	Close();
}

static int interpret_mode(File::EMode mode)
{
	if( mode == File::READ_WRITE )
	{
		return O_RDWR | O_CREAT;
	}
	else if( mode == File::WRITE )
	{
	    return O_WRONLY | O_CREAT;
	}
    else if( mode == File::WRITE_TRUNCATE )
    {
        return O_WRONLY | O_CREAT | O_TRUNC;
    }
	else
	{
	    return O_RDONLY;
	}
}

void File::Open(const std::string &filename, File::EMode mode)
{
	if( m_fd >= 0 )
	{
		THROW_ERROR( "File is already open" );
	}

    while(true)
    {
        m_fd = open( filename.c_str(), interpret_mode(mode), 0644 );
        if( m_fd < 0 )
        {
            if( errno == EINTR ) continue;

            THROW_ERROR( "Failed to open file %s (Error=%s)", filename.c_str(), strerror(errno) );
        }
        break;
    }

    LOG_DEBUG( "File '%s' opened (fd = %d)", filename.c_str(), m_fd );
}

void File::Close()
{
	if( m_fd >= 0 )
	{
		close( m_fd );
        LOG_DEBUG( "File closed (fd = %d)", m_fd );
		m_fd = -1;
	}
}

void File::Truncate(uint length)
{
    if( m_fd < 0 )
    {
        THROW_ERROR( "Can't truncate a closed file" );
    }

    ftruncate(m_fd, length);
}

bool File::Lock(File::EOpts opts, bool noblock)
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't acquire a lock on a closed file" );
	}

	int operation = 0;
	if( noblock )
	{
		operation |= LOCK_NB;
	}
	if( opts & LOCK_WRITE )
	{
		operation = LOCK_EX;
	}
	else
	{
		operation = LOCK_SH;
	}

	while(true)
	{
		int n = flock( m_fd, operation );
		if( n < 0 )
		{
			if( errno == EINTR ) continue;

			if( errno == EWOULDBLOCK ) return false;

			THROW_ERROR( "Failed to lock file (Error=%s)", strerror(errno) );
		}
		break;
	}

	return true;
}

void File::Unlock()
{
    if( m_fd < 0 )
    {
        THROW_ERROR( "Can't release a lock on a closed file" );
    }

	while(1)
	{
		int n = flock( m_fd, LOCK_UN );
		if( n < 0 )
		{
			if( errno == EINTR ) continue;

			THROW_ERROR( "Failed to unlock file (Error=%s)", strerror(errno) );
		}
		return;;
	}
}

void File::Read( ByteArray& buffer, uint size )
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't read from a closed file" );
	}

	buffer.clear();

	char inbuf[UTIL_FILE_READBUF_SIZE];

	while( size > 0 )
	{
	    size_t max_read;
	    if( size > sizeof(inbuf) )
	    {
	        max_read = sizeof(inbuf);
	    }
	    else
	    {
	        max_read = size;
	    }

		ssize_t n = read( m_fd, inbuf, max_read );
		if( n < 0 )
		{
			if( errno == EINTR ) continue;

			THROW_ERROR( "Failed to read data from the file (Error=%s)", strerror(errno) );
		}

		buffer.append( inbuf, n );
		size -= n;

		if( (size_t)n < max_read ) break;
	}
}

ByteArray File::Read(uint size)
{
    ByteArray ret;
    Read( ret, size );
    return ret;
}

void File::Write( const ByteArray& buffer )
{
    if( m_fd < 0 )
    {
        THROW_ERROR( "Can't write to a closed file" );
    }

    while(true)
    {
        ssize_t n = write( m_fd, buffer.c_str(), buffer.size() );
        if( n < 0 )
        {
            if( errno == EINTR ) continue;

            THROW_ERROR( "Failed to write data to the file (Error=%s)", strerror(errno) );
        }

        if( n != (ssize_t)buffer.size() )
        {
            THROW_ERROR( "Failed to write all data to the file" );
        }

        break;
    }
}

bool File::Exists(const std::string &filename)
{
    struct stat filestat;
    int res = stat( filename.c_str(), &filestat );
    if( res < 0 )
    {
        if( errno == ENOENT ) return false;

        THROW_ERROR( "Failed to get data about the file (Error=%s)", strerror(errno) );
    }

    return S_ISREG(filestat.st_mode);
}

std::string File::GetPath(const std::string &fullpath)
{
    uint idx = fullpath.rfind('/');
    if( idx ==  std::string::npos )
    {
        return "";
    }
    else
    {
        return fullpath.substr(0, idx+1);
    }
}

std::string File::GetFilename(const std::string &fullpath)
{
    uint idx = fullpath.rfind('/');
    if( idx ==  std::string::npos )
    {
        return fullpath;
    }
    else
    {
        return fullpath.substr(idx+1, fullpath.size()-idx-1);
    }
}
